/**
 *  练习库的测试
 *  测试大体分3类：
 *  a.单元代码测试
 *  b.文档测试
 *  c.集成测试
 * 
 *  代码测试主要有三个部分内容：
 *  1.如何编写测试
 *    主要涉及到几个编译标记/指令和几个函数/宏,Result
 *    编译指令
 *      a.#[cfg(test)]     一个条件编译指令，用于指定紧随其后的代码块或项（如函数、模块等）仅在测试环境下编译
 *      b.#[test]          函数测试标记，cargo的测试的时候，会找出有这些标记的函数，并执行测试
 *      c.#[should_panic]  函数测试标记，标记的函数会期待panic，如果函数没有panic就会测试失败。
 *    函数
 *      a.assert!      断定表达式为真。如果为真就是成功
 *      b.assert_eq!   断定两个值是否相等。如果相等就是成功
 *      c.assert_ne!   断定两个值是否不相等。如果不相等就是成功
 *      d.assert_matches! 断定表达式匹配模式。如果匹配就是成功.特别适用于枚举和结构等类型
 *      f.fail!        断定测试失败，并打印出字符串
 *      assert类函数，可以在标准参数之外再传递其它参数，比如失败时打印的字符串。rust会调用格式化字符串，并打印出参数。
 *    Result类型
 *      a.Result<T,E>  枚举类型，表示成功或失败。T为成功时的类型，E为错误类型
 *  2.如何控制测试
 *    默认的测试行为
 *      a.cargo test 在测试模式下编译代码并运行生成的测试二进制文件 
 *      b.cargo test 产生的二进制文件的默认行为是并发运行所有的测试，并截获测试运行过程中产生的输出，阻止它们被显示出来，使得阅读测试结果相关的内容变得更容易
 *    
 *    如何控制(控制cargo test的行为)
 *      a.控制并发  -- --test-threads=1  只允许一个线程运行测试
 *      b.控制输出  -- --show-output   显示测试运行过程中产生的输出。这样可以输出通过部分的打印输出
 *      c.控制测试   <name> 只运行指定名称的测试 。注意这是模糊匹配   
 *                  add  测试包含add的测试。即cargo test add
 *                 --test xxx 指定具体文件名  cargo test --test itest1    测试文件名带itest1的测试 
 *    忽略项目
 *      a.#[ignore]   在代码中标记需要忽略的内容
 *      b.测试被忽略项目  -- --ignored    ,这样只会测试被标记为#[ignore]的内容
 * 
 *    示例
 *      cargo test result  -- --test-threads=2  --show-output   在开启两个线程的情况下，测试包含result的测试，同时那些产生的输出也会显示出来
 *      cargo test -- --ignored  测试被标记为#[ignore]的内容
 *  3.测试的组织结构  
 *    如何作单元测试
 *      a.固定的规范-- 使用#[cfg(test)] 标记测试代码，并使用#[test]标记测试函数。其次模块通常也命名位tests
 *      
 *    如何作集成测试 
 *      a.在项目下创建tests目录，然后在该目录下创建测试文件  。注意tests目录和src是同级的. 这个目录应该是可以是别的，也许可以通过test的选项来指定
 *      b.在测试目录tests可以创建任意多个文件，cargo会把每一个文件当做一个单元包进行测试
 *      c.运行 cargo test即可，但是这样会把单元测试一起执行。
 *      d.通过指定集成测试文件的方式，可以只运行指定的测试文件 cargo test --test itest1.也可以通过指定测试名称的方式 
 *        carggo test xxx   
 *      综合起来，cargo test --test xxx 也许是更好的选择。
 *    
 *    集成测试的公共模块
 *      a.必须先创建一个目录，假设是haha
 *      b.在haha下创建mod.rs文件
 *      c.tests下的其它rs可以调用haha,并且haha不会被测试。haha的内容只会被集成测试代码调用。
 *      也就是说集成测试目录下的模块组织方式，反而使用的是旧的模块定义规范：基于目录+mod.rs
 * 
 *    为什么很多项目既有src/main.rs,src/lib.rs
 *      这都是为了方便测试。
 *      引入在集成测试中导入src/main.rs的模块并不容易。 main.rs是一个二进制文件，而lib.rs是库。
 *     
 * 
 * 
 * 
 */
pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::add;
    //测试宏 assert_eq!
    #[test]
    fn it_works1() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    //测试宏 assert!.并测试自定义的错误信息
    #[test]
    fn it_works2() {
        let result = add(1, 3);
        assert!(result==5,"1+3={},而不是5",result);
    }
    //测试宏 assert_ne!
    #[test]
    fn it_works3() {
        let result = add(1, 4);
        println!("这一句将在--show-output 的时候打印：1+4={}",result);
        assert_ne!(result,6);
    }
    //测试fail! 
    #[test]
    #[should_panic]
    fn it_fail() {
        //fail!("this test will fail");
        let result = add(1, 4);
        assert_eq!(result,6);
    }

    #[test]
    #[should_panic]
    fn access_outof_range() {
        let mut v = vec![1,2];  
        v[2] = 3;
    }    

    //如果返回Err，则测试失败
    #[test]
    fn test_result()->Result<(),String> {
        if 1==2 {
            Ok(())
        } else {
            Err("error".to_string())
        }
    }

    #[test]
    #[ignore]
    fn test_ignore() {
        assert_eq!(1,2, "1!={}",2);
    }
}

pub struct Point{
    pub x: u64,
    pub y: u64,
}

pub trait Tpoint{
    fn get_x(&self) -> u64;
    fn get_y(&self) -> u64;
    fn add(&self ,p:&Point)-> Point;
}